package main;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Scanner;

import javax.swing.JFileChooser;

import scanner.ScanFile;
import scanner.Token;
import token.BTree;
import token.BTreeNode;
import token.FileTuple;
import token.MiniREParser;
import token.NFATokenSpec;
import token.StringTuple;
import token.TokenSpec;
import token.Variable;
import character.DFA;
/**
 * Runs ALL THE THINGS
 * @author Justin
 *
 */
public class Driver {
	File myFile;
	Line head;
	
	static List<NFATokenSpec> NFAList = new ArrayList<NFATokenSpec>();
	static List<TokenSpec> DFAList = new ArrayList<TokenSpec>();
	
	static List<Variable> varList = new ArrayList<Variable>();
	
	/**
	 * Runs all the things.
	 * @param args
	 * @throws FileNotFoundException
	 */
	public static void main(String[] args) throws FileNotFoundException {
		try {
			JFileChooser definitionChooser = new JFileChooser();
			int value = definitionChooser.showOpenDialog(null);
			File definitionFile;
			if (value == JFileChooser.APPROVE_OPTION) {
				definitionFile = definitionChooser.getSelectedFile();
			} else {
				definitionFile = null;
				throw new IllegalStateException();
			}
			/**USING THIS.  Breaks up any given file into individual lines of code.**/
			Driver theDriver = new Driver(definitionFile);
			theDriver.scanIt();
			Line line = theDriver.getLines();	//See this here?
			MiniREParser scriptParser = new MiniREParser();
			if (!line.getLine().equals("begin")) {
				throw new IllegalStateException("PARSING ERROR: Line 1: begin not found");
			}
			int lineNumber = 2;
			line = line.getNext();
			BTreeNode head = new BTreeNode("begin");
			BTreeNode currentNode = head;
			while (line.getNext() != null) {
				currentNode.setNext(scriptParser.parseMiniRE(line.getLine(), lineNumber));
				currentNode = currentNode.getNext();
				lineNumber++;
				line = line.getNext();
			}
			if (!line.getLine().equals("end")) {
				throw new IllegalStateException("PARSING ERROR: Line " + Integer.toString(lineNumber) + ": end not found");
			}
			BTreeNode tail = new BTreeNode("end");
			currentNode.setNext(tail);
			currentNode = head;
			while(currentNode != null) {
				execute(currentNode);
				currentNode = currentNode.getNext();
			}
			
			
//			character.Parser charParser = new character.Parser();
//			line = line.getNext();
//			//Sending each individual line to the parser.
//			while(line.getLine().charAt(0) != '%') {
//				charParser.parseLine(line.getLine());
//				line = line.getNext();
//			}
//			line = line.getNext();
//			
//			token.Parser tokenParser = new token.Parser(charParser);
//			
//			while(line != null) {
//				if (line.getLine().charAt(0) != '%') {
//					NFAList.add(tokenParser.parseLine(line.getLine()));
//				}
//				line = line.getNext();
//			}
//			for (NFATokenSpec t : NFAList) {
//				TokenSpec newSpec = new TokenSpec(t.getTitle());
//				newSpec.setRepresentative(t.getRepresentative().toDFA());
//				DFAList.add(newSpec);
//			}
//			
//			JFileChooser fileChooser = new JFileChooser();
//			int returnVal = fileChooser.showOpenDialog(null);
//			File inputFile;
//			if (returnVal == JFileChooser.APPROVE_OPTION) {
//				inputFile = fileChooser.getSelectedFile();
//			} else {
//				inputFile = null;
//				throw new IllegalStateException();
//			}
//			
//			ScanFile fileScanner = new ScanFile(inputFile);
//			fileScanner.scanIt();
//			Token headToken = fileScanner.getTokens();
//			Map<Token, TokenSpec> mapList = new HashMap<Token, TokenSpec>();
//			while (headToken != null){
//				for (TokenSpec t : DFAList) {
//					if (t.getRepresentative().traverse(headToken.getWord())) {
//						mapList.put(headToken, t);
//						break;
//					}
//				}
//				
//				headToken = headToken.getNext();
//			}
//			
//			Iterator<Token> iterator = mapList.keySet().iterator();
//			while(iterator.hasNext()) {
//				Token token = iterator.next();
//				System.out.println("TOKEN " + token.getWord() +
//						" IS A " + mapList.get(token).getTitle());
//			}
		/*} catch (FileNotFoundException e) {
			System.out.println("ERROR: File handle not found");
			System.exit(-1);
			*/
		} catch (IllegalStateException e) {
			System.out.println(e.getMessage());
			System.exit(-1);
		}
	}
	
	public static void execute (BTreeNode statement) {
		if (statement.getTree().getType().equals("assignment")) {
			String id = statement.getTree().getLeft().getType();
			boolean exists = false;
			for (Variable v : varList) {
				if (v.getName().equals(id)) {
					exists = true;
					break;
				}
			}
			if (statement.getTree().getRight().getType().equals("exp")) {
				List<StringTuple> value = evalexp(statement.getTree().getRight());
				Variable newVar = new Variable(id, Variable.Var_type.STRING_LIST);
				newVar.setVar(value);
				if (exists) {
					for (int i = 0; i < varList.size(); i++) {
						Variable v = varList.get(i);
						if (v.getName().equals(id)) {
							varList.remove(i);
							i--;
						}
					}
					varList.add(newVar);
					
				} else {
					varList.add(newVar);
				}
				return;
			} else if (statement.getTree().getRight().getType().equals("numexp")) {
				List<StringTuple> value = evalexp(statement.getTree().getRight());
				Variable newVar = new Variable(id, Variable.Var_type.INTEGER);
				newVar.setVar(value.size());
				if (exists) {
					for (int i = 0; i < varList.size(); i++) {
						Variable v = varList.get(i);
						if (v.getName().equals(id)) {
							varList.remove(i);
							i--;
						}
					}
					varList.add(newVar);
					
				} else {
					varList.add(newVar);
				}
				return;
			} else if (statement.getTree().getRight().getType().equals("maxfreqstring")) {
				String varId = statement.getTree().getRight().getLeft().getType();
				Variable theVariable = null;
				for (Variable v : varList) {
					if (v.getName().equals(varId)) {
						theVariable = v;
					}
				}
				Map<String, Integer> keyMap = new HashMap<String, Integer>();
				for (StringTuple tuple : theVariable.getVar_Strings()) {
					int currentNum = 0;
					for (FileTuple fileTuple : tuple.getMetadata()) {
						for (Integer i : fileTuple.getIndices()) {
							currentNum++;
						}
					}
					keyMap.put(tuple.getValue(), currentNum);
				}
				int max = 0;
				StringTuple maxTuple = null;
				for (StringTuple tuple : theVariable.getVar_Strings()) {
					if (keyMap.get(tuple.getValue()) > max) {
						maxTuple = tuple;
					}
				}
				List<StringTuple> newValue = new ArrayList<StringTuple>();
				StringTuple copyTuple = new StringTuple();
				copyTuple.setValue(maxTuple.getValue());
				for (FileTuple fileTuple : maxTuple.getMetadata()) {
					FileTuple copyFileTuple = new FileTuple();
					copyFileTuple.setFileName(fileTuple.getFileName());
					for (Integer index : fileTuple.getIndices()) {
						copyFileTuple.addIndex(index);
					}
					copyTuple.addMetadata(copyFileTuple);
				}
				newValue.add(copyTuple);
				Variable newVar = new Variable(id, Variable.Var_type.STRING_LIST);
				newVar.setVar(newValue);
				if (exists) {
					for (int i = 0; i < varList.size(); i++) {
						Variable v = varList.get(i);
						if (v.getName().equals(id)) {
							varList.remove(i);
							i--;
						}
					}
					varList.add(newVar);
					
				} else {
					varList.add(newVar);
				}
				return;
			}
		} else if (statement.getTree().getType().equals("replace")) {
			String replacementString = statement.getTree().getLeft().getRight().getType();
			String regex = statement.getTree().getLeft().getLeft().getType();
			token.Parser tokenParser = new token.Parser(null);
			DFA newSpec = tokenParser.parseLine(regex).getRepresentative().toDFA();
			ScanFile fileDriver = new ScanFile(statement.getTree().getRight().getLeft().getType());
			fileDriver.scanIt();
			Token token = fileDriver.getTokens();
			try {
				
			
				FileWriter fileWriter = new FileWriter(statement.getTree().getRight().getRight().getType());
				
				while (token != null) {
					if (newSpec.traverse(token.getWord())) {
						fileWriter.write(replacementString + " ");
					} else {
						fileWriter.write(token.getWord() + " ");
					}
					
					
					token = token.getNext();
				}
				
				fileWriter.close();
			} catch (IOException e) {
				throw new IllegalStateException("ERROR: File I/O Failure");
			}
			return;
		}  else if (statement.getTree().getType().equals("recursivereplace")) {
			evalrecursivereplace(statement, null);
			return;
		} else if (statement.getTree().getType().equals("print")) {
			List<StringTuple> value = evalexp(statement.getTree().getLeft());
			
			evalexplisttail(statement.getTree().getRight(), value);
			return;
		} else if (statement.getTree().getType().equals("begin")) {
			return;
		} else if (statement.getTree().getType().equals("end")) {
			return;
		}
	}
	
	public static List<StringTuple> evalexp(BTree statement) {
		if (statement.getRight() == null) {
			String varId = statement.getLeft().getType();
			Variable theVariable = null;
			for (Variable v : varList) {
				if (v.getName().equals(varId)) {
					theVariable = v;
				}
			}
			if (theVariable.getType().equals(Variable.Var_type.STRING_LIST)) {
				return theVariable.getVar_Strings();
			} else {
				List<StringTuple> sizeList = new ArrayList<StringTuple>();
				StringTuple sizeTuple = new StringTuple();
				sizeTuple.setValue(Integer.toString(theVariable.getVar_Int()));
				sizeTuple.setMark();
				sizeList.add(sizeTuple);
				return sizeList;
			}
		} else {
			List<StringTuple> newValue = new ArrayList<StringTuple>();
			newValue = evalterm(statement.getLeft());
			newValue = evalexptail(statement.getRight(), newValue);
			
			return newValue;
		}
	}
	
	public static List<StringTuple> evalterm(BTree statement) {
		List<StringTuple> evaluatedTerm = new ArrayList<StringTuple>();
		
		String fileName = statement.getRight().getType();
		ScanFile fileDriver = new ScanFile(fileName);
		fileDriver.scanIt();
		Token token = fileDriver.getTokens();
		token.Parser tokenParser = new token.Parser(null);
		DFA newSpec = tokenParser.parseLine(statement.getLeft().getType()).getRepresentative().toDFA();
		
		
		int currentIndex = 0;
		while (token != null) {
			if (newSpec.traverse(token.getWord())) {
				boolean exists = false;
				for (StringTuple tuple : evaluatedTerm) {
					if (tuple.getValue().equals(token.getWord())) {
						exists = true;
						break;
					}
				}
				if (!exists) {
					String newValue = token.getWord();
					StringTuple newTuple = new StringTuple();
					newTuple.setValue(newValue);
					FileTuple newFileTuple = new FileTuple();
					newFileTuple.setFileName(fileName);
					newFileTuple.addIndex(currentIndex);
					newTuple.addMetadata(newFileTuple);
					evaluatedTerm.add(newTuple);
				} else {
					StringTuple theTuple = null;
					FileTuple theMetadata = null;
					for (StringTuple tuple : evaluatedTerm) {
						if (tuple.getValue().equals(token.getWord())) {
							theTuple = tuple;
							break;
						}
					}
					for (FileTuple metadata : theTuple.getMetadata()) {
						if (metadata.getFileName().equals(fileName)) {
							theMetadata = metadata;
							break;
						}
					}
					if (theMetadata != null) {
						theMetadata.addIndex(currentIndex);
					} else {
						theMetadata = new FileTuple();
						theMetadata.addIndex(currentIndex);
						theMetadata.setFileName(fileName);
						theTuple.addMetadata(theMetadata);
					}
				}
					
			}
			currentIndex = currentIndex + token.getWord().length() + 1;
			token = token.getNext();
		}
		return evaluatedTerm;
	}
	
	public static List<StringTuple> evalexptail(BTree statement, List<StringTuple> term) {
		if (statement.getLeft().getType().equals("empty")) {
			return term;
		} else {
			String operation = statement.getLeft().getType();
			List<StringTuple> argument = evalterm(statement.getLeft());
			if (operation.equals("diff")) {
				for (StringTuple tuple : argument) {
					for (StringTuple check : term) {
						if (check.getValue().equals(tuple.getValue())) {
							check.setMark();
						}
					}
				}
				for (int i = 0; i < term.size(); i++) {
					StringTuple tuple = term.get(i);
					if (tuple.getMark()) {
						term.remove(i);
						i--;
					}
				}
				for (StringTuple tuple : term) {
					tuple.setMarkFalse();
				}
			} else if (operation.equals("union")) {
				for (StringTuple tuple : argument) {
					term.add(tuple);
				}
			} else if (operation.equals("iners")) {
				for (StringTuple tuple : argument) {
					for (StringTuple check : term) {
						if (check.getValue().equals(tuple.getValue())) {
							check.setMark();
						}
					}
				}
				for (int i = 0; i < term.size(); i++) {
					StringTuple tuple = term.get(i);
					if (!tuple.getMark()) {
						term.remove(i);
						i--;
					}
				}
				for (StringTuple tuple : term) {
					tuple.setMarkFalse();
				}
			}
			return evalexptail(statement.getRight(), term);
		}
	}
	
	public static void evalrecursivereplace(BTreeNode statement, String filename) {
		boolean recurse = false;
		String replacementString = statement.getTree().getLeft().getRight().getType();
		String regex = statement.getTree().getLeft().getLeft().getType();
		if (regex.equals(replacementString)) {
			throw new IllegalStateException("ERROR: Recursive replace contains itself");
		}
		token.Parser tokenParser = new token.Parser(null);
		DFA newSpec = tokenParser.parseLine(regex).getRepresentative().toDFA();
		ScanFile fileDriver = null;
		if (filename == null) {
			fileDriver = new ScanFile(statement.getTree().getRight().getLeft().getType());
		} else {
			fileDriver = new ScanFile(filename);
		}
		
		fileDriver.scanIt();
		Token token = fileDriver.getTokens();
		try {
			
		
			FileWriter fileWriter = new FileWriter(statement.getTree().getRight().getRight().getType());
			
			while (token != null) {
				if (newSpec.traverse(token.getWord())) {
					recurse = true;
					fileWriter.write(replacementString + " ");
				} else {
					fileWriter.write(token.getWord() + " ");
				}
				
				
				token = token.getNext();
			}
			
			fileWriter.close();
		} catch (IOException e) {
			throw new IllegalStateException("ERROR: File I/O Failure");
		}
		if (recurse) {
			evalrecursivereplace(statement, statement.getTree().getRight().getRight().getType());
		}
	}
	
	public static void evalexplisttail(BTree statement, List<StringTuple> value) {
		if (statement.getRight() == null) {
			for (StringTuple tuple : value) {
				if (tuple.getMark()) {
					System.out.println(tuple.getValue());
				}
				System.out.println(tuple.toString());
			}
			return;
		} else {
			List<StringTuple> newValue = evalexp(statement.getLeft());
			for (StringTuple tuple : newValue) {
				value.add(tuple);
			}
			evalexplisttail(statement.getRight(), value);
		}
	}
	
	
	
	
	/**
	 * Instantiates the class, accepting a File
	 * @param File
	 */
	public Driver(File fileName){
		myFile = fileName;
	}
	/**
	 * Instantiates the class, accepting a String
	 * @param String
	 */
	public Driver(String fileName){
		myFile = new File(fileName);
	}
	/**
	 * Instantiates the class, accepting nothing
	 */
	public Driver(){
		
	}
	/**
	 * Sets the file to be used. Accepts a File
	 * @param File
	 */
	public void setFile(File fileName){
		myFile = fileName;
	}
	/**
	 * Sets the file to be used. Accepts a String
	 * @param String
	 */
	public void setFile(String fileName){
		myFile = new File(fileName);
	}
	/**
	 * Returns the current File
	 * @return File
	 */
	public File getFile(){
		return myFile;
	}
	/**
	 * Returns the first entry to the linked list of Tokens
	 * @return Token
	 */
	public Line getLines(){
		return head;
	}
	/**
	 * Prints the String given.
	 * @param s
	 */
	public void println(String s){
		System.out.println(s);
	}
	/**
	 * Scans the file provided and breaks the file into a Linked List of tokens.
	 * Ignores '%' commented lines and blank lines.
	 * Use getTokens() to obtain the list.
	 */
	public void scanIt(){
		try {
			Scanner myScanner = new Scanner(myFile);
			head = null;
			Line currentLine = null;
			while(myScanner.hasNext()){
				String theLine = myScanner.nextLine().trim(); //creates a String array from the current line
				if (!theLine.equals("")){
					if (currentLine == null){
						head = currentLine = new Line(theLine);
					}
					else{
						currentLine.setNext(new Line(theLine));
						currentLine = currentLine.getNext();
					}
				}
			}
			myScanner.close();
		} catch (FileNotFoundException e) {
			System.out.print("******************************************************************\n" + 
							"File Load Failure.\n" +
							"File \"" + myFile.getName() + "\" Should be verified for correctness.\n" + 
							"******************************************************************\n");
		}
	}
}
